<template><div><h2 id="服务定位器-service-locator" tabindex="-1"><a class="header-anchor" href="#服务定位器-service-locator"><span>服务定位器（Service Locator）</span></a></h2>
<p>服务定位器是一个了解如何提供各种应用所需的服务（或组件）的对象。在服务定位器中，每个组件都只有一个单独的实例，并通过 ID 唯一地标识。用这个 ID 就能从服务定位器中得到这个组件。</p>
<p>在 Yii 中，服务定位器是 <code v-pre>yii\di\ServiceLocator</code> 或其子类的一个实例。</p>
<p>最常用的服务定位器是 <em>application（应用）</em> 对象，可以通过 <code v-pre>\Yii::$app</code> 访问。它所提供的服务被称为 <em>application components（应用组件）</em>，比如：<code v-pre>request</code>、<code v-pre>response</code>、<code v-pre>urlManager</code> 组件。可以通过服务定位器所提供的功能，非常容易地配置这些组件，或甚至是用你自己的实现替换掉他们。</p>
<p>除了 application 对象，每个模块对象本身也是一个服务定位器。</p>
<p>要使用服务定位器，第一步是要注册相关组件。组件可以通过 [[yii\di\ServiceLocator::set()]] 方法进行注册。以下的方法展示了注册组件的不同方法：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token keyword">use</span> <span class="token package">yii<span class="token punctuation">\</span>di<span class="token punctuation">\</span>ServiceLocator</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token keyword">use</span> <span class="token package">yii<span class="token punctuation">\</span>caching<span class="token punctuation">\</span>FileCache</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token variable">$locator</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ServiceLocator</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token comment">// 通过一个可用于创建该组件的类名，注册 "cache" （缓存）组件。</span></span>
<span class="line"><span class="token variable">$locator</span><span class="token operator">-></span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'cache'</span><span class="token punctuation">,</span> <span class="token string single-quoted-string">'yii\caching\ApcCache'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token comment">// 通过一个可用于创建该组件的配置数组，注册 "db" （数据库）组件。</span></span>
<span class="line"><span class="token variable">$locator</span><span class="token operator">-></span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'db'</span><span class="token punctuation">,</span> <span class="token punctuation">[</span></span>
<span class="line">    <span class="token string single-quoted-string">'class'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">'yii\db\Connection'</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token string single-quoted-string">'dsn'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">'mysql:host=localhost;dbname=demo'</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token string single-quoted-string">'username'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">'root'</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token string single-quoted-string">'password'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">''</span><span class="token punctuation">,</span></span>
<span class="line"><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token comment">// 通过一个能返回该组件的匿名函数，注册 "search" 组件。</span></span>
<span class="line"><span class="token variable">$locator</span><span class="token operator">-></span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'search'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name class-name-fully-qualified">app<span class="token punctuation">\</span>components<span class="token punctuation">\</span>SolrService</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span>
<span class="line"><span class="token comment">// 用组件注册 "pageCache" 组件</span></span>
<span class="line"><span class="token variable">$locator</span><span class="token operator">-></span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'pageCache'</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">FileCache</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>一旦组件被注册成功，你可以任选以下两种方式之一，通过它的 ID 访问它：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token variable">$cache</span> <span class="token operator">=</span> <span class="token variable">$locator</span><span class="token operator">-></span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'cache'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line"><span class="token comment">// 或者</span></span>
<span class="line"><span class="token variable">$cache</span> <span class="token operator">=</span> <span class="token variable">$locator</span><span class="token operator">-></span><span class="token property">cache</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>如上所示，<code v-pre>yii\di\ServiceLocator</code> 允许通过组件 ID 像访问一个属性值那样访问一个组件。 当你第一次访问某组件时，<code v-pre>yii\di\ServiceLocator</code> 会通过该组件的注册信息创建一个该组件的实例，并返回它。之后，如果再次访问，则服务定位器会返回同一个实例。</p>
<p>你可以通过 [[yii\di\ServiceLocator::has()]] 检查某组件 ID 是否被注册。若你用一个无效的 ID 调用 [[yii\di\ServiceLocator::get()]]，则会抛出一个异常。</p>
<p>因为服务定位器，经常会在创建时附带 <a href="https://github.com/yiisoft/yii2/blob/master/docs/guide-zh-CN/concept-configurations.md" target="_blank" rel="noopener noreferrer">配置信息</a>，因此我们提供了一个可写的属性，名为 [[yii\di\ServiceLocator::setComponents()]]，这样就可以配置该属性，或一次性注册多个组件。下面的代码展示了如何用一个配置数组，配置一个应用并注册 <code v-pre>db</code>，<code v-pre>cache</code>，<code v-pre>tz</code> 和 <code v-pre>search</code> 组件：</p>
<div class="language-php line-numbers-mode" data-highlighter="prismjs" data-ext="php" data-title="php"><pre v-pre class="language-php"><code><span class="line"><span class="token keyword">return</span> <span class="token punctuation">[</span></span>
<span class="line">    <span class="token comment">// ...</span></span>
<span class="line">    <span class="token string single-quoted-string">'components'</span> <span class="token operator">=></span> <span class="token punctuation">[</span></span>
<span class="line">        <span class="token string single-quoted-string">'db'</span> <span class="token operator">=></span> <span class="token punctuation">[</span></span>
<span class="line">            <span class="token string single-quoted-string">'class'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">'yii\db\Connection'</span><span class="token punctuation">,</span></span>
<span class="line">            <span class="token string single-quoted-string">'dsn'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">'mysql:host=localhost;dbname=demo'</span><span class="token punctuation">,</span></span>
<span class="line">            <span class="token string single-quoted-string">'username'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">'root'</span><span class="token punctuation">,</span></span>
<span class="line">            <span class="token string single-quoted-string">'password'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">''</span><span class="token punctuation">,</span></span>
<span class="line">        <span class="token punctuation">]</span><span class="token punctuation">,</span></span>
<span class="line">        <span class="token string single-quoted-string">'cache'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">'yii\caching\ApcCache'</span><span class="token punctuation">,</span></span>
<span class="line">        <span class="token string single-quoted-string">'tz'</span> <span class="token operator">=></span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">            <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name class-name-fully-qualified"><span class="token punctuation">\</span>DateTimeZone</span><span class="token punctuation">(</span><span class="token class-name static-context">Yii</span><span class="token operator">::</span><span class="token variable">$app</span><span class="token operator">-></span><span class="token property">formatter</span><span class="token operator">-></span><span class="token property">defaultTimeZone</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">        <span class="token punctuation">}</span><span class="token punctuation">,</span></span>
<span class="line">        <span class="token string single-quoted-string">'search'</span> <span class="token operator">=></span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span>
<span class="line">            <span class="token variable">$solr</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name class-name-fully-qualified">app<span class="token punctuation">\</span>components<span class="token punctuation">\</span>SolrService</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'127.0.0.1'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span>
<span class="line">            <span class="token comment">// ... other initializations ...</span></span>
<span class="line">            <span class="token keyword">return</span> <span class="token variable">$solr</span><span class="token punctuation">;</span></span>
<span class="line">        <span class="token punctuation">}</span><span class="token punctuation">,</span></span>
<span class="line">    <span class="token punctuation">]</span><span class="token punctuation">,</span></span>
<span class="line"><span class="token punctuation">]</span><span class="token punctuation">;</span></span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在上面的代码中，有一个替代方法来配置 <code v-pre>search</code> 组件。而不是直接写一个 PHP 回调建立 <code v-pre>SolrService</code> 实例，你可以使用一个静态类方法来返回这样的回调，如下所示：</p>
<div class="language-text-html-php line-numbers-mode" data-highlighter="prismjs" data-ext="text-html-php" data-title="text-html-php"><pre v-pre class="language-text-html-php"><code><span class="line">class SolrServiceBuilder</span>
<span class="line">{</span>
<span class="line">    public static function build($ip)</span>
<span class="line">    {</span>
<span class="line">        return function () use ($ip) {</span>
<span class="line">            $solr = new app\components\SolrService($ip);</span>
<span class="line">            // ... other initializations ...</span>
<span class="line">            return $solr;</span>
<span class="line">        };</span>
<span class="line">    }</span>
<span class="line">}</span>
<span class="line"></span>
<span class="line">return [</span>
<span class="line">    // ...</span>
<span class="line">    'components' =&gt; [</span>
<span class="line">        // ...</span>
<span class="line">        'search' =&gt; SolrServiceBuilder::build('127.0.0.1'),</span>
<span class="line">    ],</span>
<span class="line">];</span>
<span class="line"></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>当你发布一个 Yii 组件封装一些非 Yii 第三方库时，这种替代方法是最好的。当您使用如上所示的静态方法来表示构建复杂逻辑的第三方对象时，您的组件用户只需要调用静态方法来配置组件。</p>
<h2 id="遍历树-tree-traversal" tabindex="-1"><a class="header-anchor" href="#遍历树-tree-traversal"><span>遍历树（Tree traversal）</span></a></h2>
<p>模块允许任意嵌套; Yii 应用程序本质上是一个模块树。由于这些模块中的每一个都是服务定位器，所以子模块有权限访问其父模块。这允许模块使用 <code v-pre>$this-&gt;get('db')</code> 而不是引用根服务定位器 <code v-pre>Yii::$app-&gt;get('db')</code>。增加的好处是开发人员可以覆盖模块中的配置。</p>
<p>如果模块无法满足要求，则从模块中检索服务的请求将被传递给它的父模块。</p>
<p>请注意，模块中组件的配置决不会与来自父模块中组件的配置合并。Service Locator 模式允许我们定义命名服务，但不能假定具有相同名称的服务使用相同的配置参数。</p>
<blockquote>
<p>💖喜欢本文档的，欢迎点赞、收藏、留言或转发，谢谢支持！<br>
作者邮箱：zhuzixian520@126.com，github地址：<a href="https://github.com/zhuzixian520" target="_blank" rel="noopener noreferrer">github.com/zhuzixian520</a></p>
</blockquote>
</div></template>


